//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Input;
using System.Resources;
using System.Collections.ObjectModel;
using Microsoft.Research.eResearch.Common.Eventing;

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    class CompositeActivityElement: TridentUIActivityElement, ICompositeUIElement
    {
        /// <summary>
        /// The offset between the ChildElements collection and the MainContentHolder's children collection.
        /// This is due to the insertion of start element.
        /// </summary>
        private readonly int MetaUiElementsOffset = 1;

        /// <summary>
        /// Connects the 2 parallel activities on the left.
        /// </summary>
        private Polyline lineLeft;

        /// <summary>
        /// Connects the 2 parallel activities on the right.
        /// </summary>
        private Polyline lineRight;

        /// <summary>
        /// Holds the main content.
        /// </summary>
        private StackPanel stkMainContentHolder;

        /// <summary>
        /// The stack header.
        /// </summary>
        private WorkflowStackHeader header;

        /// <summary>
        /// If true, the curremt model is a root element.
        /// </summary>
        private bool isRoot;

        /// <summary>
        /// The children of this composite element.
        /// </summary>
        private ObservableCollection<ITridentUIElement> childElements = new ObservableCollection<ITridentUIElement>();

        /// <summary>
        /// 
        /// </summary>
        private Brush borderColor =  new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC6D3D8"));

        /// <summary>
        /// Occurs when update is clicked.
        /// </summary>
        public override event EventHandler<ModelEventArgs> UpdateClick;

        /// <summary>
        /// UI representation for the composite activity.
        /// </summary>
        /// <param name="activity">
        /// The composite activity that the stack represents.
        /// </param>
        /// <param name="model">
        /// The activity model for the composite activity.
        /// </param>
        public CompositeActivityElement(CompositeActivityModel model, Dictionary<String, DataTypeModel> activityDataTypes)
            : base(model, activityDataTypes)
        {
            this.isRoot = (model.ParentModel == null);
            this.MetaUiElementsOffset = (model.HideStartEnd ? 0 : 1);
            InitializeComponent();
        }

        /// <summary>
        /// The stackpanel which holds the child activities.
        /// </summary>
        public StackPanel MainContentHolder
        {
            get { return stkMainContentHolder; }
        }

        /// <summary>
        /// Intialize the required component
        /// </summary>
        private new void InitializeComponent()
        {
            base.InitializeComponent();

            // Add the border which will contain the composite activity.
            this.MainBorder = new Border();
            this.MainBorder.VerticalAlignment = VerticalAlignment.Top;
            this.MainBorder.BorderBrush = this.borderColor;
            this.MainBorder.BorderThickness = new Thickness(1, 1, 2, 2);
            this.MainBorder.CornerRadius = new CornerRadius(7, 7, 7, 7);

            // Make the UIElement to be focusable.
            // this is used when we select a Binding and hit delete button.
            this.MainBorder.Focusable = true;

            // The background of the container is set to make it selectable.
            // if you have not set any background to any WPF control it will not raise any mouse down events.
            // This is by design of the controls in WPF.
            this.MainBorder.Background = Brushes.Transparent;

            StackPanel compositeActivityHolder = new StackPanel();

            // The header of the Ui element.
            this.header = new WorkflowStackHeader(this.isRoot, this.Model);
            ImageSourceConverter converter = new ImageSourceConverter();
            
            this.header.HeaderImage = (ImageSource)converter.ConvertFromString("pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/SeqActivity.png");
            this.header.HeaderBackground = Brushes.AliceBlue;
            this.header.HeaderText = this.Model.Label;
            this.header.UpdateClick += delegate(object sender, ModelEventArgs args) { this.UpdateClick.Fire(sender, args); };

            // Set the left and right connectors for the activity.
            this.LeftControlFlowConnectionElement = this.header.LeftControlFlowConnectionElement;
            this.RightControlFlowConnectionElement = this.header.RightControlFlowConnectionElement;

            compositeActivityHolder.Children.Add(this.header);

            // The body of the Element.
            Grid activityBody = new Grid();
            activityBody.MinHeight = 50;

            ColumnDefinition colLeftBranch = new ColumnDefinition();
            colLeftBranch.Width = new GridLength(20, GridUnitType.Auto);
            ColumnDefinition colMainContentHolder = new ColumnDefinition();
            colMainContentHolder.Width = new GridLength(1, GridUnitType.Auto);
            ColumnDefinition colRightBranch = new ColumnDefinition();
            colRightBranch.Width = new GridLength(20, GridUnitType.Star);

            activityBody.ColumnDefinitions.Add(colLeftBranch);
            activityBody.ColumnDefinitions.Add(colMainContentHolder);
            activityBody.ColumnDefinitions.Add(colRightBranch);

            this.lineLeft = new Polyline();
            activityBody.Children.Add(this.lineLeft);
            this.stkMainContentHolder = new StackPanel();
            activityBody.Children.Add(stkMainContentHolder);
            this.lineRight = new Polyline();
            activityBody.Children.Add(this.lineRight);

            Grid.SetColumn(this.lineLeft, 0);
            Grid.SetColumn(stkMainContentHolder, 1);
            Grid.SetColumn(this.lineRight, 2);

            // The lines for the paralell activity.
            this.lineLeft.Stroke = this.borderColor;
            this.lineLeft.StrokeThickness = 1.5;
            this.lineRight.Stroke = this.borderColor;
            this.lineRight.StrokeThickness = 1.5;

            CompositeActivityModel compositeModel = this.Model as CompositeActivityModel;
            if (compositeModel.ActivityClass.Equals(UIDesignerCodeConstants.ACTIVITYCLASSPARALLEL))
            {
                this.stkMainContentHolder.Orientation = Orientation.Vertical;
                foreach (BaseModel model in compositeModel.Children)
                {
                    if (model.IsSimpleActivityModel)
                    {
                        (model as SimpleActivityModel).DeleteBehavior = ActivityDeleteBehavior.DeleteChildren;
                    }
                    else if (model.IsCompositeActivityModel)
                    {
                        model.AllowInsertIntoSubsection = false;
                        (model as CompositeActivityModel).DeleteBehavior = ActivityDeleteBehavior.DeleteChildren;
                    }
                }
            }
            else
            {
                this.stkMainContentHolder.Orientation = Orientation.Horizontal;
            }

            this.lineLeft.Visibility = Visibility.Collapsed;
            this.lineLeft.HorizontalAlignment = HorizontalAlignment.Right;
            this.lineRight.Visibility = Visibility.Collapsed;
            this.lineRight.HorizontalAlignment = HorizontalAlignment.Left;

            activityBody.Background = Brushes.Transparent;
            activityBody.Margin = new Thickness(5);

            compositeActivityHolder.Children.Add(activityBody);
            this.MainBorder.Child = compositeActivityHolder;
            this.contentHolder.Children.Add(this.MainBorder);
            if (compositeModel.ParentModel != null && compositeModel.ParentModel.IsCompositeActivityModel)
            {
                CompositeActivityModel compositeParentActModel = compositeModel.ParentModel as CompositeActivityModel;
                if (compositeParentActModel.ActivityClass.Equals(UIDesignerCodeConstants.ACTIVITYCLASSPARALLEL))
                {
                    this.Margin = new Thickness(10, 5, 5, 12);
                }
            }
        }

        /// <summary>
        /// Apply seleted style to the workflow activity element.
        /// </summary>
        public override void ApplySelectedStyle()
        {
            // Apply the background of the border.
            this.MainBorder.BorderBrush = Brushes.Orange;
        }

        /// <summary>
        /// Apply Unseleted stlye to the workflow activity element.
        /// </summary>
        public override void ApplyUnselectedStyle()
        {
            // Revert back all the changes made while selecting.
            this.MainBorder.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC6D3D8"));
        }

        /// <summary>
        /// Update the branch connector positions.
        /// </summary>
        public void UpdateBranchPositions()
        {
            this.UpdateLayout();
            FrameworkElement prevChildElement = null;
            FrameworkElement nextChildElement = null;
            this.lineLeft.Visibility = Visibility.Visible;
            this.lineRight.Visibility = Visibility.Visible;

            if (this.stkMainContentHolder.Children.Count > 1 && this.lineLeft != null && this.lineRight != null)
            {
                this.lineLeft.Points.Clear();
                this.lineRight.Points.Clear();
                for (int counter = 0; counter < this.stkMainContentHolder.Children.Count - 1; counter++)
                {
                    // Get the point positions based on the child elements positions.
                    prevChildElement = (FrameworkElement)this.stkMainContentHolder.Children[counter];
                    nextChildElement = (FrameworkElement)this.stkMainContentHolder.Children[counter + 1];

                    Point startPointLeft = prevChildElement.TranslatePoint(new Point(0, prevChildElement.ActualHeight / 2.0), this.stkMainContentHolder);
                    Point endPointLeft = nextChildElement.TranslatePoint(new Point(0, nextChildElement.ActualHeight / 2.0), this.stkMainContentHolder);

                    double inputOffXLeft = startPointLeft.X - 11.0;
                    double outputOffXLeft = endPointLeft.X - 11.0;
                    Point inputOffPointLeft = new Point(inputOffXLeft, startPointLeft.Y);
                    Point outputOffPointLeft = new Point(outputOffXLeft, endPointLeft.Y);

                    this.lineLeft.Points.Add(startPointLeft);
                    this.lineLeft.Points.Add(inputOffPointLeft);
                    this.lineLeft.Points.Add(outputOffPointLeft);
                    this.lineLeft.Points.Add(endPointLeft);

                    Point startPointRight = new Point(startPointLeft.X, startPointLeft.Y);
                    Point endPointRight = new Point(endPointLeft.X, endPointLeft.Y);

                    double inputOffXRight = startPointRight.X + 11.0;
                    double outputOffXRight = endPointRight.X + 11.0;
                    Point inputOffPointRight = new Point(inputOffXRight, startPointRight.Y);
                    Point outputOffPointRight = new Point(outputOffXRight, endPointRight.Y);

                    this.lineRight.Points.Add(startPointRight);
                    this.lineRight.Points.Add(inputOffPointRight);
                    this.lineRight.Points.Add(outputOffPointRight);
                    this.lineRight.Points.Add(endPointRight);
                }

                this.lineLeft.Margin = new Thickness(0, 0, -10, 0);
                this.lineRight.Margin = new Thickness(-15, 0, 0, 0);
           
                Grid.SetColumn(this.lineLeft, 0);
                Grid.SetColumn(this.lineRight, 2);
            }
        }

        #region ICompositeUiElement Members

        protected virtual ObservableCollection<ITridentUIElement> GetChildElements()
        {
            return this.childElements;
        }

        public ObservableCollection<ITridentUIElement> ChildElements
        {
            get
            {
                return this.GetChildElements();
            }
        }

        public void AddChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            this.GetChildElements().Add(childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.stkMainContentHolder.Children.Add(uiElem);
            }
        }

        public void InsertChildElement(ITridentUIElement childElement, int indexToInsertAt)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            if (0 > indexToInsertAt)
            {
                throw new ArgumentOutOfRangeException("indexToInsertAt");
            }

            #endregion Parameters check.

            this.GetChildElements().Insert(indexToInsertAt, childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.stkMainContentHolder.Children.Insert(indexToInsertAt + this.MetaUiElementsOffset, uiElem);
            }
        }

        public bool RemoveChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            bool opStatus = false;
            if (this.GetChildElements().Contains(childElement))
            {
                opStatus = this.GetChildElements().Remove(childElement);
            }

            if (opStatus)
            {
                UIElement uiElem = childElement as UIElement;
                if (uiElem != null && this.stkMainContentHolder.Children.Contains(uiElem))
                {
                    this.stkMainContentHolder.Children.Remove(uiElem);
                }
            }

            return opStatus;
        }

        public void RemoveChildElementAt(int indexOfChild)
        {
            #region Parameters check.

            if (0 > indexOfChild || (this.childElements.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            if (0 > indexOfChild || (this.stkMainContentHolder.Children.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            #endregion Parameters check.

            this.GetChildElements().RemoveAt(indexOfChild);
            this.stkMainContentHolder.Children.RemoveAt(indexOfChild + this.MetaUiElementsOffset);
        }

        #endregion

        public override Point GetBindingStartPoint(UIElement relativeTo)
        {
            return new Point();
        }

        public override Point GetBindingEndpoint(UIElement relativeTo)
        {
            return new Point();
        }

        #region ICompositeUiElement Members

        Panel ICompositeUIElement.MainContentHolder
        {
            get { return this.MainContentHolder; }
        }

        #endregion
    }
}
